package com.minigame.repository.ext;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.minigame.annotation.BusID;
import com.minigame.datastore.DataStoreService;
import com.minigame.entity.CacheEntity;
import com.minigame.exception.GameDataRuntimeException;
import com.minigame.repository.ext.filter.IDataFilter;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Id;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class MultiCacheRepositoryImpl <T,ID extends Serializable,BusId extends Serializable> extends SimpleJpaRepository<T,Serializable> implements IMultiCacheRepository<T,Serializable,BusId>{
    private Method idGetMethod;
    private Method busGetMethod;



    private LoadingCache<BusId, CacheEntity<Map<ID,T>>> loadingCache;

    public MultiCacheRepositoryImpl(Class<T> domainClass, EntityManager entityManager) throws NoSuchMethodException {
        super(domainClass, entityManager);

        Field[] fields = domainClass.getDeclaredFields();
        for(Field e : fields){
            Id id = e.getAnnotation(Id.class);
            if(id != null){
                idGetMethod = domainClass.getMethod(buildFieldGetName(e));
            }
            BusID busID = e.getAnnotation(BusID.class);
            if(busID != null){
                busGetMethod = domainClass.getMethod(buildFieldGetName(e));
            }
        }

        CacheLoader<BusId,CacheEntity<Map<ID,T>>> cacheLoader = new CacheLoader<BusId,CacheEntity<Map<ID,T>>>() {
            @Override
            public @Nullable CacheEntity<Map<ID,T>> load(BusId busId) {
                Specification<T> specification = findByFieldName(busGetMethod.getName(), busId);
                List<T> list = findAll(specification);
                Map<ID,T> map = new ConcurrentHashMap<>();
                if(list != null && list.size() > 0){
                    for(T t : list){
                        try {
                            ID id = (ID) idGetMethod.invoke(t);
                            map.put(id,t);
                        } catch (Exception e) {
                            throw new GameDataRuntimeException(e);
                        }

                    }
                }

                CacheEntity<Map<ID,T>> cacheEntity = new CacheEntity(map);
                return cacheEntity;
            }

        };
        loadingCache = Caffeine.newBuilder().build(cacheLoader);
    }

    public  Specification<T> findByFieldName(String fieldName, Serializable id) {
        if(fieldName == null || id == null){
            throw new GameDataRuntimeException("fieldName="+fieldName+" id="+id+" all can't empty");
        }
        return (root, criteriaQuery, criteriaBuilder) -> {
            return criteriaBuilder.equal(root.get(fieldName), id); // 构建等于条件的Predicate

        };
    }


    private String buildFieldGetName(Field field){
        String fieldName = field.getName();
        return  "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }


    @Override
    public List<T> cacheLoadList(BusId busId) {
        CacheEntity<Map<ID,T>> cacheEntity = loadingCache.get(busId);
        Map<ID,T> entityMap = cacheEntity.getV();
        List<T> list = null;
        if(entityMap != null && entityMap.size() > 0){
            List<T> templist = new ArrayList<>();
            entityMap.forEach((key,value)->{
                templist.add(value);
            });
            list = templist;
        }
        return list;
    }

    @Override
    public List<T> cacheLoadList(BusId busId, IDataFilter<T> dataFilter) {
        CacheEntity<Map<ID,T>> cacheEntity = loadingCache.get(busId);
        Map<ID,T> entityMap = cacheEntity.getV();
        List<T> list = null;
        if(entityMap != null && entityMap.size() > 0){
            List<T> templist = new ArrayList<>();
            entityMap.forEach((key,value)->{
                if(dataFilter.checkCondition(value)){
                    templist.add(value);
                }
            });
            list = templist;
        }
        return list;
    }

    @Override
    public T cacheLoad(BusId busId, Serializable id) {
        CacheEntity<Map<ID,T>> cacheEntity = loadingCache.get(busId);
        Map<ID,T> entityMap = cacheEntity.getV();
        if(entityMap != null){
            return entityMap.get(id);
        }
        return null;
    }

    @Override
    public void cacheInsert(BusId busId, T t) {
        CacheEntity<Map<ID,T>> cacheEntity = loadingCache.get(busId);
        Map<ID,T> entityMap = cacheEntity.getV();
        ID id = getId(t);
        entityMap.put(id,t);
        DataStoreService.getInstance().cacheInsert(this,busId,id,t);
    }

    @Override
    public void cacheUpdate(BusId busId, T t) {
        CacheEntity<Map<ID,T>> cacheEntity = loadingCache.get(busId);
        Map<ID,T> entityMap = cacheEntity.getV();
        DataStoreService.getInstance().cacheUpdate(this,busId,getId(t),t);
    }

    @Override
    public void cacheDelete(BusId busId, Serializable id) {
        CacheEntity<Map<ID,T>> cacheEntity = loadingCache.get(busId);
        Map<ID,T> entityMap = cacheEntity.getV();
        if(entityMap != null){
            T t = entityMap.remove(id);
            DataStoreService.getInstance().cacheDelete(this,busId,id,t);
        }
    }

    @Override
    public void cacheBatchDelete(BusId busId) {
        CacheEntity<Map<ID,T>> cacheEntity = loadingCache.get(busId);
        Map<ID,T> entityMap = cacheEntity.getV();
        if(entityMap != null){
            Iterator<Map.Entry<ID,T>> iterator = entityMap.entrySet().iterator();
            while (iterator.hasNext()){
                Map.Entry<ID,T> entry = iterator.next();
                iterator.remove();
                DataStoreService.getInstance().cacheDelete(this,busId,entry.getKey(),entry.getValue());
            }
        }
    }

    public ID getId(T t) {
        try {
            return (ID) idGetMethod.invoke(t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
